/*******************************************************************************
 * Copyright (c) 2008, 2013 EclipseSource.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Ralf Sternberg - initial implementation and API
 ******************************************************************************/
package org.eclipse.rap.json;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.eclipse.rap.json.JsonObject.Member;


/**
 * Represents a JSON object, i.e. an unordered set of name/value pairs, where the names are strings
 * and the values are JSON values.
 * <p>
 * Members can be added using the <code>add(String, ...)</code> methods which accept instances of
 * {@link JsonValue}, strings, primitive numbers, and boolean values. To modify certain values of an
 * object, use the <code>set(String, ...)</code> methods. Please note that the <code>add</code>
 * methods are faster than <code>set</code> as they do not search for existing members. On the other
 * hand, the <code>add</code> methods do not prevent adding multiple members with the same name, as
 * duplicate names are discouraged but not prohibited by JSON.
 * </p>
 * <p>
 * Members can be accessed by their name using {@link #get(String)}. A list of all names can be
 * obtained from the method {@link #names()}. This class also supports iterating over the members in
 * document order using an {@link #iterator()} or an enhanced for loop:
 * </p>
 *
 * <pre>
 * for( Member member : jsonObject ) {
 *   String name = member.getName();
 *   JsonValue value = member.getValue();
 *   ...
 * }
 * </pre>
 * <p>
 * Even though JSON objects are unordered by definition, instances of this class preserve the order
 * of members to allow processing in document order and to guarantee a predictable output.
 * </p>
 * <p>
 * Note that this class is <strong>not thread-safe</strong>. If multiple threads access a
 * <code>JsonObject</code> instance concurrently, while at least one of these threads modifies the
 * contents of this object, access to the instance must be synchronized externally. Failure to do so
 * may lead to an inconsistent state.
 * </p>
 * <p>
 * This class is <strong>not supposed to be extended</strong> by clients.
 * </p>
 * @since 2.1
 */
@SuppressWarnings( "serial" ) // use default serial UID
public class JsonObject extends JsonValue implements Iterable<Member> {

  private final List<String> names;
  private final List<JsonValue> values;
  private transient HashIndexTable table;

  /**
   * Creates a new empty JsonObject.
   */
  public JsonObject() {
    names = new ArrayList<String>();
    values = new ArrayList<JsonValue>();
    table = new HashIndexTable();
  }

  /**
   * Creates a new JsonObject, initialized with the contents of the specified JSON object.
   *
   * @param object
   *          the JSON object to get the initial contents from, must not be <code>null</code>
   */
  public JsonObject( JsonObject object ) {
    this( object, false );
  }

  private JsonObject( JsonObject object, boolean unmodifiable ) {
    if( object == null ) {
      throw new NullPointerException( "object is null" );
    }
    if( unmodifiable ) {
      names = Collections.unmodifiableList( object.names );
      values = Collections.unmodifiableList( object.values );
    } else {
      names = new ArrayList<String>( object.names );
      values = new ArrayList<JsonValue>( object.values );
    }
    table = new HashIndexTable();
    updateHashIndex();
  }

  /**
   * Reads a JSON object from the given reader.
   * <p>
   * Characters are read in chunks and buffered internally, therefore wrapping an existing reader in
   * an additional <code>BufferedReader</code> does <strong>not</strong> improve reading
   * performance.
   * </p>
   *
   * @param reader
   *          the reader to read the JSON object from
   * @return the JSON object that has been read
   * @throws IOException
   *           if an I/O error occurs in the reader
   * @throws ParseException
   *           if the input is not valid JSON
   * @throws UnsupportedOperationException
   *           if the input does not contain a JSON object
   */
  public static JsonObject readFrom( Reader reader ) throws IOException {
    return JsonValue.readFrom( reader ).asObject();
  }

  /**
   * Reads a JSON object from the given string.
   *
   * @param string
   *          the string that contains the JSON object
   * @return the JSON object that has been read
   * @throws ParseException
   *           if the input is not valid JSON
   * @throws UnsupportedOperationException
   *           if the input does not contain a JSON object
   */
  public static JsonObject readFrom( String string ) {
    return JsonValue.readFrom( string ).asObject();
  }

  /**
   * Returns an unmodifiable JsonObject for the specified one. This method allows to provide
   * read-only access to a JsonObject.
   * <p>
   * The returned JsonObject is backed by the given object and reflect changes that happen to it.
   * Attempts to modify the returned JsonObject result in an
   * <code>UnsupportedOperationException</code>.
   * </p>
   *
   * @param object
   *          the JsonObject for which an unmodifiable JsonObject is to be returned
   * @return an unmodifiable view of the specified JsonObject
   */
  public static JsonObject unmodifiableObject( JsonObject object ) {
    return new JsonObject( object, true );
  }

  /**
   * Adds a new member at the end of this object, with the specified name and the JSON
   * representation of the specified <code>int</code> value.
   * <p>
   * This method <strong>does not prevent duplicate names</strong>. Adding a member with a name that
   * already exists in the object will add another member with the same name. In order to replace
   * existing members, use the method <code>set(name, value)</code> instead. However, <strong>
   * <em>add</em> is much faster than <em>set</em></strong> (because it does not need to search for
   * existing members). Therefore <em>add</em> should be preferred when constructing new objects.
   * </p>
   *
   * @param name
   *          the name of the member to add
   * @param value
   *          the value of the member to add
   * @return the object itself, to enable method chaining
   * @since 2.2
   */
  public JsonObject add( String name, int value ) {
    add( name, valueOf( value ) );
    return this;
  }

  /**
   * Adds a new member at the end of this object, with the specified name and the JSON
   * representation of the specified <code>long</code> value.
   * <p>
   * This method <strong>does not prevent duplicate names</strong>. Adding a member with a name that
   * already exists in the object will add another member with the same name. In order to replace
   * existing members, use the method <code>set(name, value)</code> instead. However, <strong>
   * <em>add</em> is much faster than <em>set</em></strong> (because it does not need to search for
   * existing members). Therefore <em>add</em> should be preferred when constructing new objects.
   * </p>
   *
   * @param name
   *          the name of the member to add
   * @param value
   *          the value of the member to add
   * @return the object itself, to enable method chaining
   */
  public JsonObject add( String name, long value ) {
    add( name, valueOf( value ) );
    return this;
  }

  /**
   * Adds a new member at the end of this object, with the specified name and the JSON
   * representation of the specified <code>float</code> value.
   * <p>
   * This method <strong>does not prevent duplicate names</strong>. Adding a member with a name that
   * already exists in the object will add another member with the same name. In order to replace
   * existing members, use the method <code>set(name, value)</code> instead. However, <strong>
   * <em>add</em> is much faster than <em>set</em></strong> (because it does not need to search for
   * existing members). Therefore <em>add</em> should be preferred when constructing new objects.
   * </p>
   *
   * @param name
   *          the name of the member to add
   * @param value
   *          the value of the member to add
   * @return the object itself, to enable method chaining
   */
  public JsonObject add( String name, float value ) {
    add( name, valueOf( value ) );
    return this;
  }

  /**
   * Adds a new member at the end of this object, with the specified name and the JSON
   * representation of the specified <code>double</code> value.
   * <p>
   * This method <strong>does not prevent duplicate names</strong>. Adding a member with a name that
   * already exists in the object will add another member with the same name. In order to replace
   * existing members, use the method <code>set(name, value)</code> instead. However, <strong>
   * <em>add</em> is much faster than <em>set</em></strong> (because it does not need to search for
   * existing members). Therefore <em>add</em> should be preferred when constructing new objects.
   * </p>
   *
   * @param name
   *          the name of the member to add
   * @param value
   *          the value of the member to add
   * @return the object itself, to enable method chaining
   */
  public JsonObject add( String name, double value ) {
    add( name, valueOf( value ) );
    return this;
  }

  /**
   * Adds a new member at the end of this object, with the specified name and the JSON
   * representation of the specified <code>boolean</code> value.
   * <p>
   * This method <strong>does not prevent duplicate names</strong>. Adding a member with a name that
   * already exists in the object will add another member with the same name. In order to replace
   * existing members, use the method <code>set(name, value)</code> instead. However, <strong>
   * <em>add</em> is much faster than <em>set</em></strong> (because it does not need to search for
   * existing members). Therefore <em>add</em> should be preferred when constructing new objects.
   * </p>
   *
   * @param name
   *          the name of the member to add
   * @param value
   *          the value of the member to add
   * @return the object itself, to enable method chaining
   */
  public JsonObject add( String name, boolean value ) {
    add( name, valueOf( value ) );
    return this;
  }

  /**
   * Adds a new member at the end of this object, with the specified name and the JSON
   * representation of the specified string.
   * <p>
   * This method <strong>does not prevent duplicate names</strong>. Adding a member with a name that
   * already exists in the object will add another member with the same name. In order to replace
   * existing members, use the method <code>set(name, value)</code> instead. However, <strong>
   * <em>add</em> is much faster than <em>set</em></strong> (because it does not need to search for
   * existing members). Therefore <em>add</em> should be preferred when constructing new objects.
   * </p>
   *
   * @param name
   *          the name of the member to add
   * @param value
   *          the value of the member to add
   * @return the object itself, to enable method chaining
   */
  public JsonObject add( String name, String value ) {
    add( name, valueOf( value ) );
    return this;
  }

  /**
   * Adds a new member at the end of this object, with the specified name and the specified JSON
   * value.
   * <p>
   * This method <strong>does not prevent duplicate names</strong>. Adding a member with a name that
   * already exists in the object will add another member with the same name. In order to replace
   * existing members, use the method <code>set(name, value)</code> instead. However, <strong>
   * <em>add</em> is much faster than <em>set</em></strong> (because it does not need to search for
   * existing members). Therefore <em>add</em> should be preferred when constructing new objects.
   * </p>
   *
   * @param name
   *          the name of the member to add
   * @param value
   *          the value of the member to add, must not be <code>null</code>
   * @return the object itself, to enable method chaining
   */
  public JsonObject add( String name, JsonValue value ) {
    if( name == null ) {
      throw new NullPointerException( "name is null" );
    }
    if( value == null ) {
      throw new NullPointerException( "value is null" );
    }
    table.add( name, names.size() );
    names.add( name );
    values.add( value );
    return this;
  }

  /**
   * Sets the value of the member with the specified name to the JSON representation of the
   * specified <code>int</code> value. If this object does not contain a member with this name, a
   * new member is added at the end of the object. If this object contains multiple members with
   * this name, only the last one is changed.
   * <p>
   * This method should <strong>only be used to modify existing objects</strong>. To fill a new
   * object with members, the method <code>add(name, value)</code> should be preferred which is much
   * faster (as it does not need to search for existing members).
   * </p>
   *
   * @param name
   *          the name of the member to replace
   * @param value
   *          the value to set to the member
   * @return the object itself, to enable method chaining
   * @since 2.2
   */
  public JsonObject set( String name, int value ) {
    set( name, valueOf( value ) );
    return this;
  }

  /**
   * Sets the value of the member with the specified name to the JSON representation of the
   * specified <code>long</code> value. If this object does not contain a member with this name, a
   * new member is added at the end of the object. If this object contains multiple members with
   * this name, only the last one is changed.
   * <p>
   * This method should <strong>only be used to modify existing objects</strong>. To fill a new
   * object with members, the method <code>add(name, value)</code> should be preferred which is much
   * faster (as it does not need to search for existing members).
   * </p>
   *
   * @param name
   *          the name of the member to replace
   * @param value
   *          the value to set to the member
   * @return the object itself, to enable method chaining
   * @since 2.2
   */
  public JsonObject set( String name, long value ) {
    set( name, valueOf( value ) );
    return this;
  }

  /**
   * Sets the value of the member with the specified name to the JSON representation of the
   * specified <code>float</code> value. If this object does not contain a member with this name, a
   * new member is added at the end of the object. If this object contains multiple members with
   * this name, only the last one is changed.
   * <p>
   * This method should <strong>only be used to modify existing objects</strong>. To fill a new
   * object with members, the method <code>add(name, value)</code> should be preferred which is much
   * faster (as it does not need to search for existing members).
   * </p>
   *
   * @param name
   *          the name of the member to add
   * @param value
   *          the value of the member to add
   * @return the object itself, to enable method chaining
   * @since 2.2
   */
  public JsonObject set( String name, float value ) {
    set( name, valueOf( value ) );
    return this;
  }

  /**
   * Sets the value of the member with the specified name to the JSON representation of the
   * specified <code>double</code> value. If this object does not contain a member with this name, a
   * new member is added at the end of the object. If this object contains multiple members with
   * this name, only the last one is changed.
   * <p>
   * This method should <strong>only be used to modify existing objects</strong>. To fill a new
   * object with members, the method <code>add(name, value)</code> should be preferred which is much
   * faster (as it does not need to search for existing members).
   * </p>
   *
   * @param name
   *          the name of the member to add
   * @param value
   *          the value of the member to add
   * @return the object itself, to enable method chaining
   * @since 2.2
   */
  public JsonObject set( String name, double value ) {
    set( name, valueOf( value ) );
    return this;
  }

  /**
   * Sets the value of the member with the specified name to the JSON representation of the
   * specified <code>boolean</code> value. If this object does not contain a member with this name,
   * a new member is added at the end of the object. If this object contains multiple members with
   * this name, only the last one is changed.
   * <p>
   * This method should <strong>only be used to modify existing objects</strong>. To fill a new
   * object with members, the method <code>add(name, value)</code> should be preferred which is much
   * faster (as it does not need to search for existing members).
   * </p>
   *
   * @param name
   *          the name of the member to add
   * @param value
   *          the value of the member to add
   * @return the object itself, to enable method chaining
   * @since 2.2
   */
  public JsonObject set( String name, boolean value ) {
    set( name, valueOf( value ) );
    return this;
  }

  /**
   * Sets the value of the member with the specified name to the JSON representation of the
   * specified string. If this object does not contain a member with this name, a new member is
   * added at the end of the object. If this object contains multiple members with this name, only
   * the last one is changed.
   * <p>
   * This method should <strong>only be used to modify existing objects</strong>. To fill a new
   * object with members, the method <code>add(name, value)</code> should be preferred which is much
   * faster (as it does not need to search for existing members).
   * </p>
   *
   * @param name
   *          the name of the member to add
   * @param value
   *          the value of the member to add
   * @return the object itself, to enable method chaining
   * @since 2.2
   */
  public JsonObject set( String name, String value ) {
    set( name, valueOf( value ) );
    return this;
  }

  /**
   * Sets the value of the member with the specified name to the specified JSON value. If this
   * object does not contain a member with this name, a new member is added at the end of the
   * object. If this object contains multiple members with this name, only the last one is changed.
   * <p>
   * This method should <strong>only be used to modify existing objects</strong>. To fill a new
   * object with members, the method <code>add(name, value)</code> should be preferred which is much
   * faster (as it does not need to search for existing members).
   * </p>
   *
   * @param name
   *          the name of the member to add
   * @param value
   *          the value of the member to add, must not be <code>null</code>
   * @return the object itself, to enable method chaining
   * @since 2.2
   */
  public JsonObject set( String name, JsonValue value ) {
    if( name == null ) {
      throw new NullPointerException( "name is null" );
    }
    if( value == null ) {
      throw new NullPointerException( "value is null" );
    }
    int index = indexOf( name );
    if( index != -1 ) {
      values.set( index, value );
    } else {
      table.add( name, names.size() );
      names.add( name );
      values.add( value );
    }
    return this;
  }

  /**
   * Removes a member with the specified name from this object. If this object contains multiple
   * members with the given name, only the last one is removed. If this object does not contain a
   * member with the specified name, the object is not modified.
   *
   * @param name
   *          the name of the member to remove
   * @return the object itself, to enable method chaining
   */
  public JsonObject remove( String name ) {
    if( name == null ) {
      throw new NullPointerException( "name is null" );
    }
    int index = indexOf( name );
    if( index != -1 ) {
      table.remove( index );
      names.remove( index );
      values.remove( index );
    }
    return this;
  }

  /**
   * Returns the value of the member with the specified name in this object. If this object contains
   * multiple members with the given name, this method will return the last one.
   *
   * @param name
   *          the name of the member whose value is to be returned
   * @return the value of the last member with the specified name, or <code>null</code> if this
   *         object does not contain a member with that name
   */
  public JsonValue get( String name ) {
    if( name == null ) {
      throw new NullPointerException( "name is null" );
    }
    int index = indexOf( name );
    return index != -1 ? values.get( index ) : null;
  }

  /**
   * Returns the number of members (i.e. name/value pairs) in this object.
   *
   * @return the number of members in this object
   */
  public int size() {
    return names.size();
  }

  /**
   * Returns <code>true</code> if this object contains no members.
   *
   * @return <code>true</code> if this object contains no members
   */
  public boolean isEmpty() {
    return names.isEmpty();
  }

  /**
   * Returns a list of the names in this object in document order. The returned list is backed by
   * this object and will reflect subsequent changes. It cannot be used to modify this object.
   * Attempts to modify the returned list will result in an exception.
   *
   * @returns a list of the names in this object
   */
  public List<String> names() {
    return Collections.unmodifiableList( names );
  }

  /**
   * Returns an iterator over the members of this object in document order. The returned iterator
   * cannot be used to modify this object.
   *
   * @return an iterator over the members of this object
   * @since 2.2
   */
  public Iterator<Member> iterator() {
    final Iterator<String> namesIterator = names.iterator();
    final Iterator<JsonValue> valuesIterator = values.iterator();
    return new Iterator<Member>() {

      public boolean hasNext() {
        return namesIterator.hasNext();
      }

      public Member next() {
        String name = namesIterator.next();
        JsonValue value = valuesIterator.next();
        return new Member( name, value );
      }

      public void remove() {
        throw new UnsupportedOperationException();
      }

    };
  }

  @Override
  protected void write( JsonWriter writer ) throws IOException {
    writer.writeObject( this );
  }

  @Override
  public boolean isObject() {
    return true;
  }

  @Override
  public JsonObject asObject() {
    return this;
  }

  @Override
  public int hashCode() {
    int result = 1;
    result = 31 * result + names.hashCode();
    result = 31 * result + values.hashCode();
    return result;
  }

  @Override
  public boolean equals( Object obj ) {
    if( this == obj ) {
      return true;
    }
    if( obj == null ) {
      return false;
    }
    if( getClass() != obj.getClass() ) {
      return false;
    }
    JsonObject other = (JsonObject)obj;
    return names.equals( other.names ) && values.equals( other.values );
  }

  int indexOf( String name ) {
    int index = table.get( name );
    if( index != -1 && name.equals( names.get( index ) ) ) {
      return index;
    }
    return names.lastIndexOf( name );
  }

  private synchronized void readObject( ObjectInputStream inputStream ) throws IOException,
      ClassNotFoundException
  {
    inputStream.defaultReadObject();
    table = new HashIndexTable();
    updateHashIndex();
  }

  private void updateHashIndex() {
    int size = names.size();
    for( int i = 0; i < size; i++ ) {
      table.add( names.get( i ), i );
    }
  }

  /**
   * Represents a member of a JSON object, i.e. a pair of name and value.
   * @since 2.2
   */
  public static class Member {

    private final String name;
    private final JsonValue value;

    Member( String name, JsonValue value ) {
      this.name = name;
      this.value = value;
    }

    /**
     * Returns the name of this member.
     *
     * @return the name of this member, never <code>null</code>
     */
    public String getName() {
      return name;
    }

    /**
     * Returns the value of this member.
     *
     * @return the value of this member, never <code>null</code>
     */
    public JsonValue getValue() {
      return value;
    }

    @Override
    public int hashCode() {
      int result = 1;
      result = 31 * result + name.hashCode();
      result = 31 * result + value.hashCode();
      return result;
    }

    @Override
    public boolean equals( Object obj ) {
      if( this == obj ) {
        return true;
      }
      if( obj == null ) {
        return false;
      }
      if( getClass() != obj.getClass() ) {
        return false;
      }
      Member other = (Member)obj;
      return name.equals( other.name ) && value.equals( other.value );
    }

  }

  static class HashIndexTable {

    private final byte[] hashTable = new byte[32]; // must be a power of two

    public HashIndexTable() {
    }

    public HashIndexTable( HashIndexTable original ) {
      System.arraycopy( original.hashTable, 0, hashTable, 0, hashTable.length );
    }

    void add( String name, int index ) {
      int slot = hashSlotFor( name );
      if( index < 0xff ) {
        // increment by 1, 0 stands for empty
        hashTable[slot] = (byte)( index + 1 );
      } else {
        hashTable[slot] = 0;
      }
    }

    void remove( int index ) {
      for( int i = 0; i < hashTable.length; i++ ) {
        if( hashTable[i] == index + 1 ) {
          hashTable[i] = 0;
        } else if( hashTable[i] > index + 1 ) {
          hashTable[i]--;
        }
      }
    }

    int get( Object name ) {
      int slot = hashSlotFor( name );
      // subtract 1, 0 stands for empty
      return ( hashTable[slot] & 0xff ) - 1;
    }

    private int hashSlotFor( Object element ) {
      return element.hashCode() & hashTable.length - 1;
    }

  }

}
